﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using WeifenLuo.WinFormsUI.Docking;
using MacomberMapSystem.Common.User_Interfaces.Database;
using MacomberMapSystem.Common.Serialization;
using System.Xml;
using MacomberMapSystem.Common.CIM;
using MacomberMapSystem.Common.Database;
using MacomberMapSystem.Common.Internals;
using MacomberMapSystem.Common.Integration;
using System.Runtime.InteropServices;
using System.Reflection;
using MacomberMapSystem.Common.CIM.Direct;
using System.IO;

namespace MacomberMapSystem.Common.User_Interfaces.Mapping
{
    /// <summary>
    /// This form provides an interface for validating coordinates.
    /// </summary>
    public partial class frmValidateCoordinates : DockContent
    {
        #region Variable declarations
        /// <summary>Our repository</summary>
        private MM_Repository Repository;

        /// <summary>Our stack of undo actions</summary>
        private MM_UndoRedoStack<MM_Action> UndoStack = new MM_UndoRedoStack<MM_Action>(100);

        /// <summary>Our stack of redo actions</summary>
        private MM_UndoRedoStack<MM_Action> RedoStack = new MM_UndoRedoStack<MM_Action>(100);

        /// <summary>Our currently-selected model.</summary>
        private MM_Database_Model SelectedModel;
     
        /// <summary>A timer for ensuring we don't have too many text updates</summary>
        private Timer TextTimer = new Timer();

        private Dictionary<MM_Line, ListViewItem> LineItems = new Dictionary<MM_Line, ListViewItem>();
        private Dictionary<MM_Substation, ListViewItem> SubstationItems = new Dictionary<MM_Substation, ListViewItem>();
        private List<ListViewItem> ItemsToDisplay = new List<ListViewItem>();
        private ListViewGroup SubGroup = new ListViewGroup("Substation");
        private ListViewGroup LineGroup = new ListViewGroup("Line");

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        static extern uint SendMessage(IntPtr hWnd, uint Msg, uint wParam, uint lParam);
        #endregion

        /// <summary>
        /// Initialize a new coordinates validator
        /// </summary>
        /// <param name="Repository"></param>
        public frmValidateCoordinates(MM_Repository Repository)
        {
            InitializeComponent();
            TextTimer = new Timer();
            TextTimer.Interval = 1000;
            TextTimer.Tick += new EventHandler(TextTimer_Tick);
            this.Repository = Repository;
            nmMain.InitializeRepository(Repository);
            nmMain.ElementMouseDown += new MouseEventHandler(ctlNetworkMap1_ElementMouseDown);
            nmMain.ElementMouseMove += new MouseEventHandler(ctlNetworkMap1_ElementMouseMove);
            nmMain.PropertyUpdated += new ctlNetworkMap.ElementEventDelegate(nmMain_PropertyUpdated);
            mmMain.LinkMap(nmMain);
            nmMain.DisplayOptions.MapTileStyleChanged += new EventHandler(DisplayOptions_MapTileStyleChanged);
            nmMain.DisplayOptions.MapTransparencyChanged += new EventHandler(DisplayOptions_MapTransparencyChanged);
            foreach (String MapType in Enum.GetNames(typeof(MM_MapTile.MapType)))
                btnMap.DropDownItems.Add(MapType);
            DisplayOptions_MapTileStyleChanged(null, EventArgs.Empty);
            DisplayOptions_MapTransparencyChanged(null, EventArgs.Empty);

            //Build our list of elements
            foreach (MM_Substation Sub in Repository.Db.Substations.Values)
            {
                ListViewItem NewItem = BuildItem(Sub);
                SubstationItems.Add(Sub, NewItem);
                nmMain.AddOrUpdate(Sub);
            }

            foreach (MM_Line Line in Repository.Db.Lines.Values)
            {
                ListViewItem NewItem = BuildItem(Line);                               
                LineItems.Add(Line, NewItem);
                nmMain.AddOrUpdate(Line);
            }
            RefreshComponents();
        }

        


        /// <summary>
        /// Biuld a new list view item
        /// </summary>
        /// <param name="NewElem"></param>        
        /// <returns></returns>
        private ListViewItem BuildItem(MM_Element NewElem)
        {
            if (NewElem is MM_Substation)
            {
                MM_Substation Sub = NewElem as MM_Substation;
                ListViewItem NewItem = new ListViewItem(MM_Type_Converter.TitleCase(Sub.Name));
                NewItem.SubItems.Add(MM_Type_Converter.TitleCase(Sub.LongName));
                StringBuilder sB = new StringBuilder();
                if (Sub.BusNumbers != null)
                    foreach (int BusNumber in Sub.BusNumbers)
                        sB.Append((sB.Length == 0 ? "" : ", ") + BusNumber.ToString());
                NewItem.SubItems.Add(sB.ToString());
                NewItem.SubItems.Add(MM_Type_Converter.TitleCase(Sub.Boundary == null ? "" : Sub.Boundary.Name));
                NewItem.SubItems.Add(Sub.rdfID.ToString());
                NewItem.Tag = Sub;
                NewItem.Group = SubGroup;
                return NewItem;
            }
            else if (NewElem is MM_Line)
            {
                MM_Line Line = NewElem as MM_Line;
                ListViewItem NewItem = new ListViewItem(MM_Type_Converter.TitleCase(Line.Name));
                NewItem.SubItems.Add(MM_Type_Converter.TitleCase(Line.FromSubstation.Equals(Line.ToSubstation) ? Line.FromSubstation.LongName : Line.FromSubstation.LongName + " to " + Line.ToSubstation.LongName));
                NewItem.SubItems.Add(Line.BusNumber.ToString());
                if (Line.FromBoundary == null || Line.ToBoundary == null)
                    NewItem.SubItems.Add("??");
                else
                    NewItem.SubItems.Add(MM_Type_Converter.TitleCase(Line.FromBoundary.Equals(Line.ToBoundary) ? Line.FromBoundary.Name : Line.FromBoundary.Name + " to " + Line.ToBoundary.Name));
                NewItem.SubItems.Add(Line.rdfID.ToString());
                NewItem.Tag = Line;
                NewItem.Group = LineGroup;
                return NewItem;
            }
            else
                throw new InvalidOperationException("Unable to create network map-level component for " + NewElem.GetType());
        }

        /// <summary>
        /// Update our transparency value
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DisplayOptions_MapTransparencyChanged(object sender, EventArgs e)
        {
            pbTransparency.Value = (int)(nmMain.DisplayOptions.MapTransparency * 100f);
        }


        /// <summary>
        /// When our text update timer ticks, stop.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void TextTimer_Tick(object sender, EventArgs e)
        {
            TextTimer.Stop();
            RefreshComponents();            
        }


        /// <summary>
        /// Handle a mouse move event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ctlNetworkMap1_ElementMouseMove(object sender, MouseEventArgs e)
        {
            if (sender is MM_NetworkMap_Substation)
            {
                MM_NetworkMap_Substation MovingSubstation = sender as MM_NetworkMap_Substation;
                
                //Determine the updated lat/lng coordinate
                PointD UpdatedLatLng = MM_NetworkMap_Display.XYToLatLng(MovingSubstation.SubXY = new Point(nmMain.DisplayOptions.TopLeftXY.X + e.X, nmMain.DisplayOptions.TopLeftXY.Y + e.Y), nmMain.DisplayOptions.ZoomLevel);                                                             
                QueueUndoEvent(MovingSubstation.BaseStation, "LatLong", MovingSubstation.BaseStation.LatLong, UpdatedLatLng);
                nmMain.Invalidate();
            }
            else if (sender is KeyValuePair<MM_NetworkMap_Line, int>)
            {
                KeyValuePair<MM_NetworkMap_Line, int> kvp = (KeyValuePair<MM_NetworkMap_Line, int>)sender;
                PointD[] Coords = (PointD[])kvp.Key.BaseLine.Coordinates.Clone();
                Coords[kvp.Value] =  MM_NetworkMap_Display.XYToLatLng(new Point(nmMain.DisplayOptions.TopLeftXY.X + e.X, nmMain.DisplayOptions.TopLeftXY.Y + e.Y), nmMain.DisplayOptions.ZoomLevel);
                QueueUndoEvent(kvp.Key.BaseLine, "Coordinates", kvp.Key.BaseLine.Coordinates, Coords);
                kvp.Key.RecomputeLine(nmMain.DisplayOptions);
                nmMain.Invalidate();

            }
                    
        }

        /// <summary>
        /// Handle the change of a property of our item
        /// </summary>
        /// <param name="Element"></param>
        /// <param name="PropertyName"></param>
        /// <param name="NewValue"></param>
        private void nmMain_PropertyUpdated(MM_Element Element, string PropertyName, object NewValue)
        {
        
        }

        /// <summary>
        /// Handle a mouse element down
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ctlNetworkMap1_ElementMouseDown(object sender, MouseEventArgs e)
        {
            //throw new NotImplementedException();
        }

        #region Undo/redo stack
        /// <summary>
        /// Add or update an undo event into our queue
        /// </summary>
        /// <param name="ModifiedElement">The modified element</param>
        /// <param name="PropertyName">The modified property for that element</param>
        /// <param name="OldValue">The old value</param>
        /// <param name="NewValue">The new value</param>
        public void QueueUndoEvent(MM_Element ModifiedElement, String PropertyName, Object OldValue, Object NewValue)
        {
          
                Object[] InObj = ModifiedElement.GetType().GetMember(PropertyName);
                if (InObj.Length != 1)
                    return;
                else if (InObj[0] is FieldInfo)
                    (InObj[0] as FieldInfo).SetValue(ModifiedElement, NewValue);
                else if (InObj[0] is System.Reflection.PropertyInfo)
                    (InObj[0] as PropertyInfo).SetValue(ModifiedElement, NewValue, null);
                        

            //When substation coordinates are updated, also update line coordinates
                if (ModifiedElement is MM_Substation && PropertyName == "LatLong")
                {
                    MM_NetworkMap_Substation MovingSubstation = nmMain.Substations[(MM_Substation)ModifiedElement];
                    PointD UpdatedLatLng = (PointD)NewValue;
                   
                    //Run through our lines to update
                    foreach (KeyValuePair<MM_Line, MM_NetworkMap_Line> kvp in nmMain.Lines)
                        if (kvp.Key.FromSubstation == MovingSubstation.BaseStation)
                        {
                            kvp.Key.Coordinates[0] = UpdatedLatLng;
                            kvp.Value.RecomputeLine(nmMain.DisplayOptions);
                        }
                        else if (kvp.Key.ToSubstation == MovingSubstation.BaseStation)
                        {
                            kvp.Key.Coordinates[kvp.Key.Coordinates.Length - 1] = UpdatedLatLng;
                            kvp.Value.RecomputeLine(nmMain.DisplayOptions);
                        }                                                                     
                }

            /*
            if (UndoStack.Count > 0)
            {
                MM_Action LastAction = UndoStack.Peek();
                if (LastAction.ModifiedElement == ModifiedElement && LastAction.PropertyName == PropertyName)
                {
                    LastAction.AssignNewValue(NewValue);
                    return;
                }
            }
            UndoStack.Push(new MM_Action(ModifiedElement, PropertyName, OldValue, NewValue));

            if (PropertyName == "Add")
            {
                if (!Elements.ContainsKey(ModifiedElement))
                    if (ModifiedElement is MM_Substation)
                        Elements.Add(ModifiedElement, new MM_Editor_Substation((MM_Substation)ModifiedElement));
                    else if (ModifiedElement is MM_Line)
                        Elements.Add(ModifiedElement, new MM_Editor_Line((MM_Line)ModifiedElement));

                Elements[ModifiedElement].AddComponent(lvComponents);
                lvComponents.Sort();
            }

            else if (PropertyName == "Remove")
            {
                //lvComponents.Items.Remove(Elements[ModifiedElement].AssociatedItem);
                //Elements.Remove(ModifiedElement);
            }

            else if (PropertyName == "LatLong" && float.IsNaN(((PointD)OldValue).X) && !float.IsNaN(((PointD)NewValue).X))
            {                
                Repository.Db.Substations.Add(ModifiedElement.rdfID, ModifiedElement as MM_Substation);                
                //Elements[ModifiedElement].AddComponent(lvComponents);
            }
            btnUndo.Enabled = true;*/
           
        }

        /// <summary>
        /// Undo a user event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnUndo_Click(object sender, EventArgs e)
        {
            /*if (UndoStack.Count > 0)
            {
                MM_Action Action = UndoStack.Pop();
                Action.AssignOldValue();
                RedoStack.Push(Action);
                btnRedo.Enabled = true;
                btnUndo.Enabled = UndoStack.Count > 0;
                nMap.Invalidate();

                if (Action.PropertyName == "Remove")
                {
                    if (!Elements.ContainsKey(Action.ModifiedElement))
                        if (Action.ModifiedElement is MM_Substation)
                            Elements.Add(Action.ModifiedElement, new MM_Editor_Substation((MM_Substation)Action.ModifiedElement));
                        else if (Action.ModifiedElement is MM_Line)
                            Elements.Add(Action.ModifiedElement, new MM_Editor_Line((MM_Line)Action.ModifiedElement));

                    Elements[Action.ModifiedElement].AddComponent(lvComponents);
                    lvComponents.Sort();
                }
                else if (Action.PropertyName == "Add")
                {
                    lvComponents.Items.Remove(Elements[Action.ModifiedElement].AssociatedItem);
                    if (Action.ModifiedElement is MM_Substation && MM_Repository.Substations.ContainsKey(Action.ModifiedElement.Name))
                        MM_Repository.Substations.Remove(Action.ModifiedElement.Name);
                    if (Action.ModifiedElement is MM_Line && MM_Repository.Lines.ContainsKey(Action.ModifiedElement.Name))
                        MM_Repository.Lines.Remove(Action.ModifiedElement.Name);
                    if (MM_Repository.TEIDs.ContainsKey(Action.ModifiedElement.TEID))
                        MM_Repository.TEIDs.Remove(Action.ModifiedElement.TEID);
                    if (MM_Repository.MismatchedTEIDs.ContainsKey(Action.ModifiedElement.TEID))
                        MM_Repository.TEIDs.Remove(Action.ModifiedElement.TEID);
                }

                else if (Action.PropertyName == "LatLong" && float.IsNaN(((PointD)Action.OldValue).X) && !float.IsNaN(((PointD)Action.NewValue).X))
                {
                    MM_Repository.MismatchedTEIDs.Add(Action.ModifiedElement.TEID, Action.ModifiedElement);
                    MM_Repository.Substations.Remove(Action.ModifiedElement.Name);
                    MM_Repository.TEIDs.Remove(Action.ModifiedElement.TEID);
                    Elements[Action.ModifiedElement].AddComponent(lvComponents);
                }
                nMap.Invalidate();
            }*/
        }


        /// <summary>
        /// Redo a user event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRedo_Click(object sender, EventArgs e)
        {
            /*      if (RedoStack.Count > 0)
                  {
                      MM_Action Action = RedoStack.Pop();
                      Action.AssignNewValue();
                      UndoStack.Push(Action);
                      btnUndo.Enabled = true;
                      btnRedo.Enabled = RedoStack.Count > 0;



                      if (Action.PropertyName == "Add")
                      {
                          if (!Elements.ContainsKey(Action.ModifiedElement))
                              if (Action.ModifiedElement is MM_Substation)
                                  Elements.Add(Action.ModifiedElement, new MM_Editor_Substation((MM_Substation)Action.ModifiedElement));
                              else if (Action.ModifiedElement is MM_Line)
                                  Elements.Add(Action.ModifiedElement, new MM_Editor_Line((MM_Line)Action.ModifiedElement));

                          Elements[Action.ModifiedElement].AddComponent(lvComponents);
                          lvComponents.Sort();
                      }
                      else if (Action.PropertyName == "Remove")
                      {
                          lvComponents.Items.Remove(Elements[Action.ModifiedElement].AssociatedItem);
                          if (Action.ModifiedElement is MM_Substation && MM_Repository.Substations.ContainsKey(Action.ModifiedElement.Name))
                              MM_Repository.Substations.Remove(Action.ModifiedElement.Name);
                          if (Action.ModifiedElement is MM_Line && MM_Repository.Lines.ContainsKey(Action.ModifiedElement.Name))
                              MM_Repository.Lines.Remove(Action.ModifiedElement.Name);
                          if (MM_Repository.TEIDs.ContainsKey(Action.ModifiedElement.TEID))
                              MM_Repository.TEIDs.Remove(Action.ModifiedElement.TEID);
                          if (MM_Repository.MismatchedTEIDs.ContainsKey(Action.ModifiedElement.TEID))
                              MM_Repository.TEIDs.Remove(Action.ModifiedElement.TEID);
                      }

                      else if (Action.PropertyName == "LatLong" && !float.IsNaN(((PointD)Action.OldValue).X) && float.IsNaN(((PointD)Action.NewValue).X))
                      {
                          MM_Repository.MismatchedTEIDs.Remove(Action.ModifiedElement.TEID);
                          MM_Repository.Substations.Add(Action.ModifiedElement.Name, (MM_Substation)Action.ModifiedElement);
                          MM_Repository.TEIDs.Add(Action.ModifiedElement.TEID, Action.ModifiedElement);
                          Elements[Action.ModifiedElement].AddComponent(lvComponents);
                      }
                      nMap.Invalidate();
                  }*/
        }
        #endregion

      

        #region User Interactions
        /// <summary>
        /// Handle the selection of a model.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSelectModel_Click(object sender, EventArgs e)
        {
            using (frmSelectModel ModelSelector = new frmSelectModel(Repository))
                if (ModelSelector.ShowDialog() == DialogResult.OK)
                {
                    if (ModelSelector.SelectedModel.State != MM_Database_Model.enumModelState.Completed)
                        if (MessageBox.Show("Open model " + ModelSelector.SelectedModel.Name + "?", Application.ProductName, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
                            return;
                        else if (ModelSelector.DirectMode)
                            ModelSelector.SelectedModel = CIM_DirectModel.OpenModel(ModelSelector.SelectedModel, Repository, Repository.Model_Collection, null);
                        else
                            ModelSelector.SelectedModel.InitiateLoading();
                    lvElements.Items.Clear();

                    //Now that the model has loaded, handle accordingly
                    while (ModelSelector.SelectedModel.State != MM_Database_Model.enumModelState.Completed)
                        Application.DoEvents();

                    foreach (MM_Element NewElem in nmMain.InitializeModel(ModelSelector.SelectedModel))
                        if (NewElem is MM_Substation)
                            SubstationItems.Add(NewElem as MM_Substation, BuildItem(NewElem));
                        else if (NewElem is MM_Line)
                            LineItems.Add(NewElem as MM_Line, BuildItem(NewElem));
                    RefreshComponents();
                    SelectedModel = ModelSelector.SelectedModel;
                    btnSelectModel.Text = SelectedModel.FullClass;
                    
                }
        }

        /// <summary>
        /// Refresh our collection of components
        /// </summary>
        private void RefreshComponents()
        {
            //Produce our list of substations and lines
            List<ListViewItem> NewList = new List<ListViewItem>();
            if (String.IsNullOrEmpty(txtFilter.Text))
            {
                NewList.AddRange(SubstationItems.Values);
                NewList.AddRange(LineItems.Values);
            }
            else
            {
                Dictionary<MM_Substation, bool> IncludedSubs = new Dictionary<MM_Substation, bool>();
                foreach (KeyValuePair<MM_Substation, ListViewItem> kvp in SubstationItems)
                    if (kvp.Key.Name.IndexOf(txtFilter.Text, StringComparison.CurrentCultureIgnoreCase) != -1 || kvp.Key.LongName.IndexOf(txtFilter.Text, StringComparison.CurrentCultureIgnoreCase) != -1)
                    {
                        IncludedSubs.Add(kvp.Key, true);
                        NewList.Add(kvp.Value);
                    }
                foreach (KeyValuePair<MM_Line, ListViewItem> kvp in LineItems)
                    if (IncludedSubs.ContainsKey(kvp.Key.FromSubstation) || IncludedSubs.ContainsKey(kvp.Key.ToSubstation) || kvp.Key.Name.IndexOf(txtFilter.Text, StringComparison.CurrentCultureIgnoreCase) != -1)
                        NewList.Add(kvp.Value);
            }
            List<ListViewItem> ToDispose = this.ItemsToDisplay;
            this.ItemsToDisplay = NewList;
            this.lvElements.VirtualListSize = NewList.Count;
            this.lvElements.Refresh();
        }
        #endregion

        /// <summary>
        /// Offer the right-click menu for our element
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lvElements_MouseUp(object sender, MouseEventArgs e)
        {
            ListViewHitTestInfo hti = lvElements.HitTest(e.Location);
            if (hti.Item != null && e.Button == MouseButtons.Right)
            {
                cmsListView.Items.Clear();
                cmsListView.Tag = hti.Item.Tag;
                if (hti.Item.Tag is MM_Substation)
                    (hti.Item.Tag as MM_Substation).WriteMenuElement(cmsListView);
                else if (hti.Item.Tag is MM_Line)
                    (hti.Item.Tag as MM_Line).WriteMenuElements(cmsListView);
                else if (hti.Item.Tag is CIM_Element)
                    (hti.Item.Tag as CIM_Element).WriteMenuElement(cmsListView.Items, cmsListView);                
                cmsListView.Items.Add("-");
                cmsListView.Items.Add("Move map");
                cmsListView.Items.Add("Paste coordinates from clipboard").Tag = hti.Item.SubItems[1].Tag;

                cmsListView.Show(lvElements, e.Location);
            }
        }

        /// <summary>
        /// Handle a menu item click
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void cmsListView_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            String[] PasteText;
            if (e.ClickedItem.Text.StartsWith("Paste"))
                PasteText = Clipboard.GetText(TextDataFormat.Text).Replace('\t', ',').Replace("\r\n", ",").Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
            else
                PasteText = new string[0];

            if (e.ClickedItem.Text == "Move map")
            {
                object SelObj = cmsListView.Tag;
                if (SelObj is MM_Substation)
                    nmMain.SetCenter((SelObj as MM_Substation).LatLong);
                else if (SelObj is MM_Line)
                {
                    PointD[] LatLng = (SelObj as MM_Line).Coordinates;
                    nmMain.SetCenter(LatLng[LatLng.Length / 2]);
                }
            }
            else if (e.ClickedItem.Text == "Paste coordinates from clipboard")
                if (cmsListView.Tag is MM_Substation)
                {
                    MM_Substation SelSub = cmsListView.Tag as MM_Substation;
                    SelSub = Repository.Db.Substations[SelSub.rdfID];
                    
                    if (PasteText.Length == 2)
                    {
                        PointD UpdatedLatLng;
               
                        if (double.Parse(PasteText[1]) > 0)
                            UpdatedLatLng=new PointD(double.Parse(PasteText[0]), double.Parse(PasteText[1]));
                        else                            
                            UpdatedLatLng=new PointD(double.Parse(PasteText[1]), double.Parse(PasteText[0]));
                        QueueUndoEvent(SelSub, "LatLong", SelSub, UpdatedLatLng);
                        nmMain.MoveSubstation(SelSub, UpdatedLatLng);
                        nmMain.SetCenter(SelSub.LatLong);
                    }
                }
                else if (cmsListView.Tag is MM_Line)
                {
                    MM_Line NewLine = cmsListView.Tag as MM_Line;
                    List<PointD> InPoints = new List<PointD>();
                    for (int a = 0; a < PasteText.Length; a += 2)
                        InPoints.Add(new PointD(double.Parse(PasteText[a]), double.Parse(PasteText[a + 1])));
                    PointD FirstCoord = InPoints[0];
                    PointD LastCoord = InPoints[InPoints.Count - 1];
                    if (NewLine.FromSubstation.Longitude == FirstCoord.X && NewLine.FromSubstation.Latitude == FirstCoord.Y && NewLine.ToSubstation.Longitude == LastCoord.X && NewLine.ToSubstation.Latitude == LastCoord.Y)
                    {
                        NewLine.Coordinates = InPoints.ToArray();
                        nmMain.SetCenter(InPoints[InPoints.Count / 2]);
                        NewLine.ValidatedBy = Repository.CurrentUser;
                        NewLine.ValidatedOn = DateTime.Now;
                        NewLine.FindBoundaries(Repository);
                        NewLine.UpdateRdb(Repository);
                        nmMain.AddOrUpdate(NewLine);
                    }
                    else if (NewLine.ToSubstation.Longitude == FirstCoord.X && NewLine.ToSubstation.Latitude == FirstCoord.Y && NewLine.FromSubstation.Longitude == LastCoord.X && NewLine.FromSubstation.Latitude == LastCoord.Y)
                    {
                        PointD[] OutPoints = new PointD[InPoints.Count];
                        for (int a = 0; a < InPoints.Count; a++)
                            OutPoints[a] = InPoints[InPoints.Count - a - 1];
                        NewLine.Coordinates = InPoints.ToArray();
                        NewLine.FindBoundaries(Repository);
                        NewLine.ValidatedBy = Repository.CurrentUser;
                        NewLine.ValidatedOn = DateTime.Now;
                        nmMain.SetCenter(InPoints[InPoints.Count / 2]);
                        NewLine.UpdateRdb(Repository);
                        nmMain.AddOrUpdate(NewLine);
                    }
                    else
                        MessageBox.Show("The start and end coordinates don't match the known substation coordinates!", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Stop);
             
                }
                else if (cmsListView.Tag is CIM_Element && (cmsListView.Tag as CIM_Element).TypeName == "cim:ACLineSegment" && PasteText.Length >= 4)
                {
                    List<PointD> InPoints = new List<PointD>();
                    for (int a = 0; a < PasteText.Length; a += 2)
                        InPoints.Add(new PointD(double.Parse(PasteText[a]), double.Parse(PasteText[a + 1])));
                    CIM_Element BaseLine = cmsListView.Tag as CIM_Element;
                    MM_Line NewLine = new MM_Line(BaseLine, Repository);

                    PointD FirstCoord = InPoints[0];
                    PointD LastCoord = InPoints[InPoints.Count - 1];
                    
                    
                    if (NewLine.FromSubstation.Longitude == FirstCoord.X && NewLine.FromSubstation.Latitude == FirstCoord.Y && NewLine.ToSubstation.Longitude == LastCoord.X && NewLine.ToSubstation.Latitude == LastCoord.Y)
                    {
                        NewLine.Coordinates = InPoints.ToArray();
                        nmMain.SetCenter(InPoints[InPoints.Count / 2]);
                        NewLine.ValidatedBy = Repository.CurrentUser;
                        NewLine.ValidatedOn = DateTime.Now;
                        NewLine.FindBoundaries(Repository);
                        NewLine.WriteRdb(Repository);
                        nmMain.AddOrUpdate(NewLine);
                        Repository.Db.Lines.Add(NewLine.rdfID, NewLine);
                        //MapObjects[e.ClickedItem.Tag as string] = NewLine;
                    }
                    else if (NewLine.ToSubstation.Longitude == FirstCoord.X && NewLine.ToSubstation.Latitude == FirstCoord.Y && NewLine.FromSubstation.Longitude == LastCoord.X && NewLine.FromSubstation.Latitude == LastCoord.Y)
                    {
                        PointD[] OutPoints = new PointD[InPoints.Count];
                        for (int a = 0; a < InPoints.Count; a++)
                            OutPoints[a] = InPoints[InPoints.Count - a - 1];
                        NewLine.Coordinates = InPoints.ToArray();
                        NewLine.FindBoundaries(Repository);
                        NewLine.ValidatedBy = Repository.CurrentUser;
                        NewLine.ValidatedOn = DateTime.Now;
                        nmMain.SetCenter(InPoints[InPoints.Count / 2]);
                        NewLine.WriteRdb(Repository);
                        nmMain.AddOrUpdate(NewLine);
                        Repository.Db.Lines.Add(NewLine.rdfID, NewLine);
                        //MapObjects[e.ClickedItem.Tag as string] = NewLine;
                    }
                    else
                        MessageBox.Show("The start and end coordinates don't match the known substation coordinates!", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Stop);
                }
                else if (cmsListView.Tag is CIM_Element && (cmsListView.Tag as CIM_Element).TypeName == "cim:Substation" && PasteText.Length == 2)
                {
                    CIM_Element BaseElement = cmsListView.Tag as CIM_Element;
                    MM_Substation NewSub = new MM_Substation(BaseElement, Repository);
                    NewSub.rdfID = BaseElement.rdfID;
                    NewSub.LatLong = new PointD(double.Parse(PasteText[0]), double.Parse(PasteText[1]));
                    NewSub.FindBoundary(Repository);
                    Repository.Db.Substations.Add(NewSub.rdfID, NewSub);
                    nmMain.AddOrUpdate(NewSub);
                    nmMain.SetCenter(NewSub.LatLong);
                    NewSub.WriteRdb(Repository);
                    //MapObjects[e.ClickedItem.Tag as string] = NewSub;
                }
        }

        /// <summary>
        /// When our text changes, kick off our filter
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void txtFilter_TextChanged(object sender, EventArgs e)
        {
            TextTimer.Stop();
            TextTimer.Start();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void nmMain_Resize(object sender, EventArgs e)
        {
            mmMain.Location = new Point(nmMain.Right - mmMain.Width, nmMain.Bottom - mmMain.Height);
        }

        /// <summary>
        /// Update our preferred map style
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnMap_DropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
        {
            nmMain.DisplayOptions.MapTiles = (MM_MapTile.MapType)Enum.Parse(typeof(MM_MapTile.MapType), e.ClickedItem.Text);
        }

        /// <summary>
        /// Update our collection of map tiles to show only the selected one
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DisplayOptions_MapTileStyleChanged(object sender, EventArgs e)
        {
            foreach (ToolStripMenuItem tsi in btnMap.DropDownItems)
                tsi.Checked = tsi.Text == nmMain.DisplayOptions.MapTiles.ToString();
        }

        /// <summary>
        /// Update the transparency of our map based on the requested value
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void pbTransparency_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left)
            {
                float TargetVal = ((float)e.X + (float)pbTransparency.ContentRectangle.Left) / (float)pbTransparency.ContentRectangle.Width;
                if (TargetVal >= 0f && TargetVal <= 1f)
                    nmMain.DisplayOptions.MapTransparency = TargetVal;
                SendMessage(pbTransparency.ProgressBar.Handle, 0x400 + 16, 0x3, 0);                
            }
        }

        private void lvElements_RetrieveVirtualItem(object sender, RetrieveVirtualItemEventArgs e)
        {
            e.Item = ItemsToDisplay[e.ItemIndex];
        }


        /// <summary>
        /// Convert kml coordinates to Lat/Lng
        /// </summary>
        /// <param name="Text"></param>
        /// <returns></returns>
        private PointD[] HandleCoordinates(String Text)
        {
            List<PointD> OutPoints = new List<PointD>();
            foreach (String str in Text.Trim().Split(' '))
                if (!String.IsNullOrEmpty(str))
                {
                    String[] splStr = str.Split(',');
                    OutPoints.Add(new PointD(Double.Parse(splStr[0]), Double.Parse(splStr[1])));
                }
            return OutPoints.ToArray();

        }

        /// <summary>
        /// Load our coordinates list
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnLoadCoordinates_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog oFd = new OpenFileDialog())
            {
                oFd.Title = "Select a lat/long coordinates file";
                oFd.Filter = "Comma Delimited file (*.csv)|*.csv|Google Earth files (*.kmz, *.kml)|*.kmz;*.kml";
                oFd.Multiselect = true;
                if (oFd.ShowDialog() != DialogResult.OK)
                    return;

                //Based on our data, import our coordinates
                Dictionary<String, PointD[]> SubLatLngs = new Dictionary<string, PointD[]>(StringComparer.CurrentCultureIgnoreCase);
                Dictionary<String, PointD[]> LineLatLngs = new Dictionary<string, PointD[]>(StringComparer.CurrentCultureIgnoreCase);
                                            PointD[] FoundLatLng;

                foreach (String FileName in oFd.FileNames)
                    if (Path.GetExtension(FileName) == ".kmz")
                        using (Ionic.Zip.ZipFile zF = Ionic.Zip.ZipFile.Read(FileName))
                            foreach (Ionic.Zip.ZipEntry zE in zF.Entries)
                            {
                                XmlDocument xDoc = new XmlDocument();
                                using (Stream sRd = zE.OpenReader())
                                    xDoc.Load(sRd);
                                //Now, pull in all of our placemearks
                                foreach (XmlElement xPlacemark in xDoc.SelectNodes("//*"))
                                    if (xPlacemark.Name == "Placemark" && xPlacemark["name"] != null && !xPlacemark["name"].InnerText.StartsWith("Untitled"))
                                        if (xPlacemark["Point"] != null)
                                        {
                                            if (SubLatLngs.TryGetValue(xPlacemark["name"].InnerText, out FoundLatLng))
                                            {
                                                List<PointD> Pts = new List<PointD>(FoundLatLng);
                                                Pts.Add(HandleCoordinates(xPlacemark["Point"]["coordinates"].InnerText)[0]);
                                                SubLatLngs[xPlacemark["name"].InnerText] = Pts.ToArray();
                                            }
                                            else
                                                SubLatLngs.Add(xPlacemark["name"].InnerText, new PointD[] { HandleCoordinates(xPlacemark["Point"]["coordinates"].InnerText)[0] });
                                        }
                                        else if (xPlacemark["LineString"] != null)
                                            if (LineLatLngs.ContainsKey(xPlacemark["name"].InnerText))
                                                Console.WriteLine("Duplicate for " + xPlacemark["name"].InnerText + ": " + xPlacemark["LineString"]["coordinates"].InnerText);
                                            else
                                                LineLatLngs.Add(xPlacemark["name"].InnerText, HandleCoordinates(xPlacemark["LineString"]["coordinates"].InnerText));
                            }
                    else if (Path.GetExtension(oFd.FileName) == ".csv")
                        using (StreamReader sRd = new StreamReader(FileName))
                        {
                            String[] HeaderLine = sRd.ReadLine().Split('\t');
                            int ShortName = Array.IndexOf(HeaderLine, "ShortName");
                            int Lat = Array.IndexOf(HeaderLine, "Latitude");
                            int Long = Array.IndexOf(HeaderLine, "Longitude");
                            while (!sRd.EndOfStream)
                            {
                                String[] InLine = sRd.ReadLine().Split('\t');
                                SubLatLngs.Add(InLine[ShortName], new PointD[] {new PointD(Convert.ToDouble(InLine[Long]), Convert.ToDouble(InLine[Lat]))});
                            }
                        }

                //Now, create our mappings between substation long name and name
                Dictionary<String, MM_Substation[]> SubstationLongNames = new Dictionary<string, MM_Substation[]>();
                Dictionary<CIM_RdfID, MM_Substation> SubsFromRdfID = new Dictionary<CIM_RdfID, MM_Substation>();
                foreach (MM_Substation Sub in nmMain.Substations.Keys)
                {
                    SubsFromRdfID.Add(Sub.rdfID, Sub);
                    if (SubstationLongNames.ContainsKey(Sub.LongName))
                    {
                        List<MM_Substation> Subs = new List<MM_Substation>(SubstationLongNames[Sub.LongName]);
                        Subs.Add(Sub);
                        SubstationLongNames[Sub.LongName] = Subs.ToArray();
                    }
                    else
                        SubstationLongNames.Add(Sub.LongName, new MM_Substation[] { Sub });
                }



                //Now, link up our substations to our list of long names
                List<MM_Substation> SubstationsNeedingRepositioning = new List<MM_Substation>(nmMain.Substations.Keys);
                MM_Substation[] LinkedSubs;
                CIM_Element cSub;


                //Go through each substation, find the corresponding CIM element, and process
                foreach (KeyValuePair<String, PointD[]> UpdatedSubstation in SubLatLngs)
                    if (!SubstationLongNames.TryGetValue(UpdatedSubstation.Key, out LinkedSubs))
                        Console.WriteLine("Unable to find substations to match up with " + UpdatedSubstation.Key);
                    else
                    {
                        MM_Substation Sub=null;
                        if (UpdatedSubstation.Value.Length == 1 && LinkedSubs.Length == 1)
                        {                            
                            Sub = LinkedSubs[0];
                            Sub.LatLong = UpdatedSubstation.Value[0];
                        }
                        else if (LinkedSubs.Length == 1)
                        {
                            PointD ClosestPoint = PointD.NaN;
                            Double Dist = Double.NaN;
                            Sub = LinkedSubs[0];
                            foreach (PointD Pt2 in UpdatedSubstation.Value)
                            {
                                PointD Pt1 = Sub.LatLong;
                                double R = 3963.1f; //Radius of the earth in miles
                                double deltaLat = (Pt2.Y - Pt1.Y) * Math.PI / 180f;
                                double deltaLon = (Pt2.X - Pt1.X) * Math.PI / 180f;
                                double a = Math.Sin(deltaLat / 2f) * Math.Sin(deltaLat / 2f) + Math.Cos(Pt1.Y * Math.PI / 180f) * Math.Cos(Pt2.Y * Math.PI / 180d) * Math.Sin(deltaLon / 2f) * Math.Sin(deltaLon / 2f);
                                double c = 2f * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1f - a));
                                Double ThisDist = R * c;
                                if (Double.IsNaN(Dist) || Dist > ThisDist)
                                {
                                    Dist = ThisDist;
                                    ClosestPoint = Pt2;
                                }
                            }
                            Sub.LatLong = ClosestPoint;                            
                        }
                        else if (UpdatedSubstation.Value.Length == 1)
                        {
                            /*
                            PointD ClosestPoint = PointD.NaN;
                            Double Dist = Double.NaN;
                            Sub = LinkedSubs[0];
                            foreach (PointD Pt2 in UpdatedSubstation.Value)
                            {
                                PointD Pt1 = Sub.LatLong;
                                double R = 3963.1f; //Radius of the earth in miles
                                double deltaLat = (Pt2.Y - Pt1.Y) * Math.PI / 180f;
                                double deltaLon = (Pt2.X - Pt1.X) * Math.PI / 180f;
                                double a = Math.Sin(deltaLat / 2f) * Math.Sin(deltaLat / 2f) + Math.Cos(Pt1.Y * Math.PI / 180f) * Math.Cos(Pt2.Y * Math.PI / 180d) * Math.Sin(deltaLon / 2f) * Math.Sin(deltaLon / 2f);
                                double c = 2f * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1f - a));
                                Double ThisDist = R * c;
                                if (Double.IsNaN(Dist) || Dist > ThisDist)
                                {
                                    Dist = ThisDist;
                                    ClosestPoint = Pt2;
                                }
                            }
                            Sub.LatLong = ClosestPoint;      */
                        }
                        else
                        {
                        }
                        Sub.Repositioned = true;
                        Sub.ValidatedBy = Repository.CurrentUser;
                        Sub.ValidatedOn = DateTime.Now;
                        Sub.FindBoundary(Repository);
                        nmMain.Substations[Sub].RecomputeSubstation(nmMain.DisplayOptions);
                        SubstationsNeedingRepositioning.Remove(Sub);
                    }

                        




                




                Dictionary<String, MM_Substation> MappedSubstations = new Dictionary<string, MM_Substation>();
                foreach (String str in SubLatLngs.Keys)
                    MappedSubstations.Add(str, null);
                PointD[] FoundPoint;
                foreach (KeyValuePair<MM_Substation, MM_NetworkMap_Substation> Sub in nmMain.Substations)
                    if ((cSub = SelectedModel.FromRdfID(Sub.Key.rdfID)) != null)
                        if (SubLatLngs.TryGetValue(Sub.Key.LongName, out FoundPoint))
                        {
                            if (FoundPoint.Length == 1 || (FoundPoint.Length == 2 && FoundPoint[0] == FoundPoint[1]))
                                Sub.Key.LatLong = FoundPoint[0];
                            else
                            {
                                Sub.Key.LatLong = FoundPoint[0];
                                Console.WriteLine("Duplicate substation coordinates: " + Sub.Key.Name);
                            }
                            Sub.Key.Repositioned = true;
                            Sub.Key.ValidatedBy = Repository.CurrentUser;
                            Sub.Key.ValidatedOn = DateTime.Now;
                            Sub.Key.FindBoundary(Repository);
                            Sub.Value.RecomputeSubstation(nmMain.DisplayOptions);
                            SubsFromRdfID.Add(Sub.Key.rdfID, Sub.Key);
                            MappedSubstations[Sub.Key.LongName] = Sub.Key;
                        }
                        else
                        {
                            Sub.Key.LatLong = PointD.NaN;
                            Sub.Key.Repositioned = true;
                            Sub.Key.ValidatedBy = null;
                            if (bool.Parse(cSub["etx:Substation.forNetwork"]) && bool.Parse(cSub["etx:Substation.forScada"]))
                            {
                                SubsFromRdfID.Add(Sub.Key.rdfID, Sub.Key);
                                SubstationsNeedingRepositioning.Add(Sub.Key);
                            }
                        }

                //Now, handle all substation that need repositioning get updated.   
                for (int Iteration = 0; Iteration < 5; Iteration++)
                    foreach (MM_Substation Sub in SubstationsNeedingRepositioning.ToArray())
                    {
                        PointD NewPoint = new PointD();
                        int NumPoints = 0;
                        MM_Substation mOtherSub;
                        CIM_Element cSubstation = SelectedModel.FromRdfID(Sub.rdfID);
                        if (cSubstation != null)
                            foreach (CIM_Element OtherSub in cSubstation.ConnectedStations)
                                if (SubsFromRdfID.TryGetValue(OtherSub.rdfID, out mOtherSub) && !mOtherSub.LatLong.IsNan)
                                {
                                    NewPoint.X += mOtherSub.LatLong.X;
                                    NewPoint.Y += mOtherSub.LatLong.Y;
                                    NumPoints++;
                                }

                        //If we have one point, put in a warning
                        if (NumPoints == 0)
                            Console.WriteLine("Error (" + Iteration.ToString() + "): no lat/long points were detected for " + Sub.Name + " (" + Sub.LongName + ")");
                        else if (NumPoints == 1)
                            Console.WriteLine("Warning (" + Iteration.ToString() + "): only one lat/long point was detected for " + Sub.Name + " (" + Sub.LongName + ")");
                        else
                            SubstationsNeedingRepositioning.Remove(Sub);

                        if (NumPoints > 0)
                        {
                            Sub.LatLong = new PointD(NewPoint.X / (double)NumPoints, NewPoint.Y / (double)NumPoints);
                            Sub.Repositioned = true;
                            Sub.ValidatedBy = Repository.CurrentUser;
                            Sub.ValidatedOn = DateTime.Now;
                            Sub.FindBoundary(Repository);
                            nmMain.Substations[Sub].RecomputeSubstation(nmMain.DisplayOptions);
                        }

                    }

                //Now, pull in all of our lines
                Dictionary<MM_Line, bool> ProcessedLines = new Dictionary<MM_Line, bool>();
                foreach (KeyValuePair<String, PointD[]> kvp in LineLatLngs)
                {
                    MM_Substation Sub1, Sub2;
                    string[] splStr = kvp.Key.Split(new string[] { " to " }, StringSplitOptions.RemoveEmptyEntries);
                    if (MappedSubstations.TryGetValue(splStr[0], out Sub1) && MappedSubstations.TryGetValue(splStr[1], out Sub2))
                    {
                        foreach (KeyValuePair<MM_Line, MM_NetworkMap_Line> kvp2 in nmMain.Lines)
                            if ((kvp2.Key.FromSubstation == Sub1 && kvp2.Key.ToSubstation == Sub2) || (kvp2.Key.FromSubstation == Sub2 && kvp2.Key.ToSubstation == Sub1))
                            {
                                kvp2.Key.Coordinates = kvp.Value;
                                kvp2.Key.ValidatedBy = Repository.CurrentUser;
                                kvp2.Key.ValidatedOn = DateTime.Now;
                                kvp2.Key.FindBoundaries(Repository);
                                kvp2.Value.RecomputeLine(nmMain.DisplayOptions);
                                ProcessedLines.Add(kvp2.Key, true);
                            }
                    }
                }

                //Now interpolate all lines for which we have no data
                foreach (KeyValuePair<MM_Line, MM_NetworkMap_Line> kvp2 in nmMain.Lines)
                    if (!ProcessedLines.ContainsKey(kvp2.Key))
                        if (kvp2.Key.FromSubstation.LatLong.IsNan || kvp2.Key.ToSubstation.LatLong.IsNan)
                            Console.WriteLine("Warning: Line " + kvp2.Key.Name + " between " + kvp2.Key.FromSubstation.LongName + " to " + kvp2.Key.ToSubstation.LongName + " couldn't be estimated because of invalid coordinates.");
                        else
                        {
                            kvp2.Key.Coordinates = new PointD[] { kvp2.Key.FromSubstation.LatLong, kvp2.Key.ToSubstation.LatLong };
                            kvp2.Key.ValidatedBy = Repository.CurrentUser;
                            kvp2.Key.ValidatedOn = DateTime.Now;
                            kvp2.Key.FindBoundaries(Repository);
                            kvp2.Value.RecomputeLine(nmMain.DisplayOptions);
                        }
               



            }
        }

       

        
        
    }
}